/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package checker.sdchecker;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author takeshi
 */
public abstract class PartitionTester extends Observable implements Runnable {

    protected static final byte[] TEST_DATA;

    static {
        TEST_DATA = new byte[1024 * 1024];
        byte current = 42;
        for (int i = 0; i < 1024 * 1024; i++) {
            TEST_DATA[i] = current++;
        }
    }
    private final File root;
    private final long totalSize;
    private final ExecutorService executorService;
    private volatile int totalFiles;
    private volatile int processedFiles;
    private volatile long startTime, finishTime;
    private volatile long bytesUsed;
    private volatile Exception error;
    private volatile boolean stopped;
    private volatile File[] selectedFiles;

    public PartitionTester(File root, int numberOfThreads, long size) {
        this.root = root;
        this.totalSize = size;
        this.executorService = Executors.newFixedThreadPool(numberOfThreads);
    }

    public PartitionTester(File root, int numberOfThreads) {
        this(root, numberOfThreads, root.getFreeSpace());
    }

    public PartitionTester(File root) throws IOException {
        this(root, 1);
    }

    public void forceStop() {
        stopped = true;
        this.executorService.shutdown();
    }

    protected boolean isStopped() {
        return stopped;
    }

    public void run() {
        try {
            totalFiles = getSelectedFiles().length;
            setChanged();
            notifyObservers(getResult());
            List<Callable<Void>> tasks = new ArrayList<Callable<Void>>(getSelectedFiles().length);
            for (File toUse : selectedFiles) {
                toUse.deleteOnExit();
                tasks.add(createJob(toUse));
            }
            startTime = System.currentTimeMillis();
            setChanged();
            notifyObservers(getResult());
            executorService.invokeAll(tasks);
            finishTime = System.currentTimeMillis();
            setChanged();
            notifyObservers(getResult());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            setError(e);
        } finally {
            executorService.shutdownNow();
        }
        setChanged();
        notifyObservers(getResult());
    }

    public int getProcessedFiles() {
        return processedFiles;
    }

    public int getTotalFiles() {
        return totalFiles;
    }

    protected abstract File[] selectFiles(File root) throws IOException;

    protected abstract Callable<Void> createJob(File toUse);

    protected synchronized void addBytes(long bytes) {
        this.bytesUsed += bytes;
        setChanged();
        notifyObservers(getResult());
    }

    protected File getRoot() {
        return root;
    }

    protected synchronized void fileFinished() {
        processedFiles++;
        setChanged();
        notifyObservers(getResult());
    }

    public synchronized TestResult getResult() {
        long end = finishTime > 0 ? finishTime : System.currentTimeMillis();
        return new TestResult(end - this.startTime, totalSize, totalFiles, this.bytesUsed, this.processedFiles);
    }

    public long getStartTime() {
        return startTime;
    }

    public long getBytesUsed() {
        return bytesUsed;
    }

    public long getTotalSize() {
        return totalSize;
    }

    public boolean isFinished() {
        return finishTime > 0;
    }

    public void setError(Exception error) {
        executorService.shutdownNow();
        this.error = error;
        setChanged();
        notifyObservers(error);
    }

    public Exception getError() {
        return error;
    }

    public File[] getSelectedFiles() {
        if (selectedFiles == null) {
            try {
                selectedFiles = selectFiles(root);
            } catch (IOException ex) {
                ex.printStackTrace();
                Logger.getLogger(PartitionTester.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return selectedFiles;
    }
}
